agentmux_srv\backend\wcore/
tab.rs1use uuid::Uuid;
7
8use crate::backend::storage::wstore::WaveStore;
9use crate::backend::storage::StoreError;
10use crate::backend::obj::*;
11
12pub fn create_tab(store: &WaveStore, ws_id: &str) -> Result<Tab, StoreError> {
16 create_tab_with_opts(store, ws_id, "", false)
17}
18
19pub fn create_tab_with_opts(
21 store: &WaveStore,
22 ws_id: &str,
23 tab_name: &str,
24 pinned: bool,
25) -> Result<Tab, StoreError> {
26 let mut ws = store.must_get::<Workspace>(ws_id)?;
27
28 let name = if tab_name.is_empty() {
33 format!("tab{}", ws.tabids.len() + ws.pinnedtabids.len() + 1)
34 } else {
35 tab_name.to_string()
36 };
37
38 let mut layout = LayoutState {
40 oid: Uuid::new_v4().to_string(),
41 rootnode: None,
42 magnifiednodeid: String::new(),
43 focusednodeid: String::new(),
44 leaforder: None,
45 pendingbackendactions: None,
46 meta: None,
47 ..Default::default()
48 };
49 store.insert(&mut layout)?;
50
51 let mut tab = Tab {
52 oid: Uuid::new_v4().to_string(),
53 name,
54 layoutstate: layout.oid.clone(),
55 blockids: vec![],
56 meta: MetaMapType::new(),
57 ..Default::default()
58 };
59 store.insert(&mut tab)?;
60
61 if pinned {
63 ws.pinnedtabids.push(tab.oid.clone());
64 } else {
65 ws.tabids.push(tab.oid.clone());
66 }
67 if ws.activetabid.is_empty() {
68 ws.activetabid = tab.oid.clone();
69 }
70 store.update(&mut ws)?;
71
72 Ok(tab)
73}
74
75pub fn delete_tab(
77 store: &WaveStore,
78 ws_id: &str,
79 tab_id: &str,
80) -> Result<(), StoreError> {
81 let mut ws = store.must_get::<Workspace>(ws_id)?;
82
83 ws.tabids.retain(|id| id != tab_id);
85 ws.pinnedtabids.retain(|id| id != tab_id);
86
87 if ws.activetabid == tab_id {
89 ws.activetabid = ws.tabids.first().cloned().unwrap_or_default();
90 }
91 store.update(&mut ws)?;
92
93 delete_tab_inner(store, tab_id)?;
94 Ok(())
95}
96
97pub(super) fn delete_tab_inner(store: &WaveStore, tab_id: &str) -> Result<(), StoreError> {
100 if let Ok(tab) = store.must_get::<Tab>(tab_id) {
101 for block_id in &tab.blockids {
103 crate::backend::blockcontroller::delete_controller(block_id);
104 }
105 if !tab.layoutstate.is_empty() {
107 let _ = store.delete::<LayoutState>(&tab.layoutstate);
108 }
109 for block_id in &tab.blockids {
111 let _ = store.delete::<Block>(block_id);
112 }
113 }
114 let _ = store.delete::<Tab>(tab_id);
115 Ok(())
116}
117
118pub fn set_active_tab(
120 store: &WaveStore,
121 ws_id: &str,
122 tab_id: &str,
123) -> Result<(), StoreError> {
124 let mut ws = store.must_get::<Workspace>(ws_id)?;
125 let tab_str = tab_id.to_string();
126 if !ws.tabids.contains(&tab_str) && !ws.pinnedtabids.contains(&tab_str) {
127 return Err(StoreError::NotFound);
128 }
129 ws.activetabid = tab_str;
130 store.update(&mut ws)?;
131 Ok(())
132}
133
134pub fn reorder_tab(
136 store: &WaveStore,
137 ws_id: &str,
138 tab_id: &str,
139 new_index: usize,
140) -> Result<(), StoreError> {
141 tracing::info!(ws_id = %ws_id, tab_id = %tab_id, new_index = %new_index, "[dnd] reorder_tab");
142 let mut ws = store.must_get::<Workspace>(ws_id)?;
143
144 if let Some(pos) = ws.tabids.iter().position(|id| id == tab_id) {
146 ws.tabids.remove(pos);
147 let insert_at = new_index.min(ws.tabids.len());
148 ws.tabids.insert(insert_at, tab_id.to_string());
149 } else if let Some(pos) = ws.pinnedtabids.iter().position(|id| id == tab_id) {
150 ws.pinnedtabids.remove(pos);
151 let insert_at = new_index.min(ws.pinnedtabids.len());
152 ws.pinnedtabids.insert(insert_at, tab_id.to_string());
153 } else {
154 return Err(StoreError::NotFound);
155 }
156
157 store.update(&mut ws)?;
158 tracing::info!(tab_id = %tab_id, new_index = %new_index, "[dnd] reorder_tab complete");
159 Ok(())
160}